//! 文件轮转集成测试
//!
//! 测试 RotatingFileAppender 的完整功能

#![allow(clippy::unwrap_used, clippy::expect_used)]

use log4r::appender::{Appender, RotatingFileAppender};
use log4r::{Level, LogEvent};
use std::sync::Arc;
use std::thread;
use tempfile::TempDir;

// ============================================================================
// IT-10.1-001: 完整轮转流程测试 (AC-10.1.1 #1, #2, AC-10.1.3 #6)
// ============================================================================

/// IT-10.1-001: 测试完整轮转流程
#[test]
fn test_complete_rotation_flow() {
    // GIVEN: 使用小 max_size 的 RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("app.log");

    // 100 字节触发轮转，禁用清理以测试轮转功能
    let appender = RotatingFileAppender::new(&log_path, 100)
        .expect("创建失败")
        .with_max_files(0); // 禁用清理，确保不丢失日志

    // WHEN: 写入足够多的日志触发轮转
    for i in 0..20 {
        let event = LogEvent::new(Level::Info, "test", format!("Message number {}", i));
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 应该有轮转文件生成
    let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| s.starts_with("app."))
                .unwrap_or(false)
        })
        .collect();

    assert!(
        entries.len() >= 2,
        "应该有轮转文件生成，实际文件数: {}",
        entries.len()
    );

    // 验证所有日志都被写入（无丢失）
    let mut total_lines = 0;
    for entry in &entries {
        let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
        total_lines += content.lines().count();
    }
    assert_eq!(total_lines, 20, "应有 20 条日志，无丢失");
}

// ============================================================================
// IT-10.1-002: 多线程并发轮转测试 (AC-10.1.4 #7)
// ============================================================================

/// IT-10.1-002: 测试多线程并发写入和轮转
#[test]
fn test_concurrent_rotation() {
    // GIVEN: 共享的 RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("concurrent.log");

    // 使用较小的 max_size 触发多次轮转，禁用清理以测试并发
    let appender = Arc::new(
        RotatingFileAppender::new(&log_path, 200)
            .expect("创建失败")
            .with_max_files(0), // 禁用清理，确保不丢失日志
    );
    let mut handles = vec![];

    // WHEN: 4 个线程并发写入
    for thread_id in 0..4 {
        let appender_clone = Arc::clone(&appender);
        let handle = thread::spawn(move || {
            for msg_id in 0..25 {
                let event = LogEvent::new(
                    Level::Info,
                    "concurrent",
                    format!("T{}-M{}", thread_id, msg_id),
                );
                appender_clone.write(&event).expect("写入失败");
            }
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().expect("线程 join 失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 所有日志都应被写入（共 100 条）
    let mut total_lines = 0;
    for entry in std::fs::read_dir(temp_dir.path()).unwrap().flatten() {
        if entry
            .file_name()
            .to_str()
            .map(|s| s.contains("concurrent"))
            .unwrap_or(false)
        {
            let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
            total_lines += content.lines().count();
        }
    }

    assert_eq!(total_lines, 100, "应有 100 条日志");
}

// ============================================================================
// IT-10.1-003: 轮转不丢失日志测试 (AC-10.1.3 #6)
// ============================================================================

/// IT-10.1-003: 验证轮转过程中不丢失日志
#[test]
fn test_rotation_no_data_loss() {
    // GIVEN: RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("noloss.log");

    // 禁用清理以验证轮转不丢失日志
    let appender = RotatingFileAppender::new(&log_path, 150)
        .expect("创建失败")
        .with_max_files(0); // 禁用清理

    // WHEN: 写入带唯一标识的日志
    let expected_messages: Vec<String> = (0..50).map(|i| format!("UNIQUE-{:04}", i)).collect();

    for msg in &expected_messages {
        let event = LogEvent::new(Level::Info, "test", msg.clone());
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 所有唯一标识都应存在于文件中
    let mut all_content = String::new();
    for entry in std::fs::read_dir(temp_dir.path()).unwrap().flatten() {
        if entry
            .file_name()
            .to_str()
            .map(|s| s.contains("noloss"))
            .unwrap_or(false)
        {
            all_content.push_str(&std::fs::read_to_string(entry.path()).unwrap_or_default());
        }
    }

    for msg in &expected_messages {
        assert!(all_content.contains(msg), "日志 {} 丢失", msg);
    }
}

// ============================================================================
// IT-10.1-004: Builder API with_rotation() 测试 (AC-10.1.9 #15, #16)
// ============================================================================

/// IT-10.1-004: 测试 Builder API with_rotation()
#[test]
fn test_builder_with_rotation() {
    // GIVEN: Log4rBuilder
    let builder = log4r::Log4rBuilder::new()
        .with_file("logs/test.log")
        .with_rotation("10MB");

    // THEN: Builder 应成功创建（不实际初始化，避免全局状态冲突）
    // 验证 Builder 可以链式调用
    let _ = builder;
}

// ============================================================================
// IT-10.1-005: 配置文件 max_size 初始化测试 (AC-10.1.6 #11)
// ============================================================================

/// IT-10.1-005: 测试配置文件 max_size 解析
#[test]
fn test_config_max_size_parsing() {
    // GIVEN: 包含 max_size 的配置
    use log4r::config::parse_size;

    // WHEN/THEN: 各种格式都应正确解析
    assert_eq!(parse_size("10MB").unwrap(), 10 * 1024 * 1024);
    assert_eq!(parse_size("500KB").unwrap(), 500 * 1024);
    assert_eq!(parse_size("1GB").unwrap(), 1024 * 1024 * 1024);
    assert_eq!(parse_size("1024").unwrap(), 1024);
    assert_eq!(parse_size("100B").unwrap(), 100);

    // 错误情况
    assert!(parse_size("0").is_err());
    assert!(parse_size("invalid").is_err());
    assert!(parse_size("").is_err());
}

// ============================================================================
// IT-10.1-006: 轮转失败降级测试 (AC-10.1.10 #17)
// ============================================================================

/// IT-10.1-006: 测试轮转失败时的降级行为
///
/// 注意：完整的失败场景测试（如磁盘满、权限不足）难以在单元测试中可靠模拟，
/// 因为需要操作系统级别的权限控制。此测试验证：
/// 1. 正常情况下不会 panic
/// 2. 代码中包含降级逻辑（通过代码审查确认）
///
/// 降级逻辑位置：src/appender/rotation.rs rotate() 方法
/// - 重命名失败：输出 stderr 警告，继续写入当前文件
/// - 创建新文件失败：输出 stderr 警告，尝试恢复原文件
#[test]
fn test_rotation_graceful_degradation() {
    // GIVEN: 正常的 RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("graceful.log");

    // 禁用清理以测试降级行为
    let appender = RotatingFileAppender::new(&log_path, 100)
        .expect("创建失败")
        .with_max_files(0); // 禁用清理

    // WHEN: 正常写入（验证不会 panic）
    for i in 0..30 {
        let event = LogEvent::new(Level::Info, "test", format!("Message {}", i));
        let result = appender.write(&event);
        // THEN: 写入应成功，不应 panic
        assert!(result.is_ok(), "写入应成功");
    }

    appender.flush().expect("刷新失败");

    // 验证：所有日志都被写入（无 panic 导致的丢失）
    let mut total_lines = 0;
    for entry in std::fs::read_dir(temp_dir.path()).unwrap().flatten() {
        if entry
            .file_name()
            .to_str()
            .map(|s| s.contains("graceful"))
            .unwrap_or(false)
        {
            let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
            total_lines += content.lines().count();
        }
    }
    assert_eq!(total_lines, 30, "所有日志应被写入，无 panic");
}

// ============================================================================
// 轮转文件命名测试
// ============================================================================

/// 测试轮转文件命名格式
#[test]
fn test_rotation_file_naming() {
    // GIVEN: RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("naming.log");

    // 使用很小的 max_size 触发轮转，禁用清理
    let appender = RotatingFileAppender::new(&log_path, 50)
        .expect("创建失败")
        .with_max_files(0); // 禁用清理

    // WHEN: 写入足够多的日志触发轮转
    for i in 0..10 {
        let event = LogEvent::new(Level::Info, "test", format!("Msg{}", i));
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 轮转文件应符合命名格式 {stem}.{date}.{seq}.{ext}
    let mut rotated_files = vec![];
    for entry in std::fs::read_dir(temp_dir.path()).unwrap().flatten() {
        let name = entry.file_name().to_string_lossy().to_string();
        if name.starts_with("naming.") && name != "naming.log" {
            rotated_files.push(name);
        }
    }

    // 验证轮转文件存在且命名正确
    for file in &rotated_files {
        // 格式: naming.YYYY-MM-DD.N.log
        let parts: Vec<&str> = file.split('.').collect();
        assert!(parts.len() >= 4, "轮转文件命名格式不正确: {}", file);
        assert_eq!(parts[0], "naming");
        // parts[1] 应该是日期 YYYY-MM-DD
        assert!(parts[1].len() == 10, "日期格式不正确: {}", parts[1]);
        // parts[2] 应该是序号
        assert!(
            parts[2].parse::<u32>().is_ok(),
            "序号格式不正确: {}",
            parts[2]
        );
    }
}

// ============================================================================
// 初始化时文件大小检测测试
// ============================================================================

/// 测试初始化时读取现有文件大小
#[test]
fn test_init_with_existing_file() {
    // GIVEN: 已存在的日志文件
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("existing.log");

    // 创建包含内容的文件
    let initial_content = "A".repeat(80); // 80 字节
    std::fs::write(&log_path, &initial_content).expect("写入初始内容失败");

    // WHEN: 创建 RotatingFileAppender（max_size = 100）
    let appender = RotatingFileAppender::new(&log_path, 100).expect("创建失败");

    // 验证 current_size 被正确设置
    assert_eq!(appender.current_size(), 80);

    // THEN: 再写入 30 字节应触发轮转
    let event = LogEvent::new(Level::Info, "test", "This message triggers rotation");
    appender.write(&event).expect("写入失败");
    appender.flush().expect("刷新失败");

    // 验证轮转发生
    let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| s.starts_with("existing."))
                .unwrap_or(false)
        })
        .collect();

    assert!(entries.len() >= 2, "应该触发轮转");
}

// ============================================================================
// Story 10.2: 时间轮转集成测试
// ============================================================================

/// IT-10.2-001: 完整时间轮转流程测试 (AC-10.2.1 #1, AC-10.2.9 #21)
#[test]
fn test_time_rotation_flow() {
    use log4r::RotationPeriod;
    use time::Duration;

    // GIVEN: 使用 Daily 时间轮转的 RotatingFileAppender
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("time_rotate.log");

    let appender = RotatingFileAppender::with_time_rotation(&log_path, RotationPeriod::Daily)
        .expect("创建失败");

    // 设置 last_rotation_time 为昨天（模拟日期切换）
    let yesterday = time::OffsetDateTime::now_utc() - Duration::days(1);
    appender.set_last_rotation_time_for_test(yesterday);

    // WHEN: 写入日志（应触发时间轮转）
    for i in 0..5 {
        let event = LogEvent::new(Level::Info, "test", format!("Time rotation msg {}", i));
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 应该有轮转文件生成
    let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| s.starts_with("time_rotate."))
                .unwrap_or(false)
        })
        .collect();

    assert!(
        entries.len() >= 2,
        "应该有轮转文件生成，实际文件数: {}",
        entries.len()
    );

    // 验证所有日志都被写入（无丢失）
    let mut total_lines = 0;
    for entry in &entries {
        let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
        total_lines += content.lines().count();
    }
    assert_eq!(total_lines, 5, "应有 5 条日志，无丢失");
}

/// IT-10.2-002: 时间+大小双触发测试 (AC-10.2.4 #10, #11)
#[test]
fn test_dual_trigger_rotation() {
    use log4r::RotationPeriod;

    // GIVEN: 同时启用大小和时间轮转
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("dual_trigger.log");

    let appender = RotatingFileAppender::with_config(&log_path, Some(100), RotationPeriod::Daily)
        .expect("创建失败");

    // WHEN: 写入足够多的日志触发大小轮转（时间未切换）
    for i in 0..20 {
        let event = LogEvent::new(Level::Info, "test", format!("Dual trigger msg {}", i));
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 应该有轮转文件生成（由大小触发）
    let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| s.starts_with("dual_trigger."))
                .unwrap_or(false)
        })
        .collect();

    assert!(
        entries.len() >= 2,
        "应该有轮转文件生成，实际文件数: {}",
        entries.len()
    );
}

/// IT-10.2-003: 仅时间轮转（无大小限制）测试 (AC-10.2.4 #12)
#[test]
fn test_time_only_rotation() {
    use log4r::RotationPeriod;
    use time::Duration;

    // GIVEN: 仅时间轮转（无大小限制）
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("time_only.log");

    let appender = RotatingFileAppender::with_time_rotation(&log_path, RotationPeriod::Daily)
        .expect("创建失败");

    // 验证 max_size 为 None
    assert!(appender.max_size().is_none());

    // 设置 last_rotation_time 为昨天
    let yesterday = time::OffsetDateTime::now_utc() - Duration::days(1);
    appender.set_last_rotation_time_for_test(yesterday);

    // WHEN: 写入大量日志（不会因大小触发轮转，只会因时间触发）
    for i in 0..100 {
        let event = LogEvent::new(Level::Info, "test", format!("Time only msg {}", i));
        appender.write(&event).expect("写入失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 应该只有一次轮转（由时间触发）
    let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
        .unwrap()
        .filter_map(|e| e.ok())
        .filter(|e| {
            e.file_name()
                .to_str()
                .map(|s| s.starts_with("time_only."))
                .unwrap_or(false)
        })
        .collect();

    // 应该有 2 个文件：原文件 + 1 个轮转文件
    assert_eq!(entries.len(), 2, "应该只有一次时间轮转");
}

/// IT-10.2-004: 多线程并发时间轮转测试 (AC-10.2.10 #22)
#[test]
fn test_concurrent_time_rotation() {
    use log4r::RotationPeriod;
    use time::Duration;

    // GIVEN: 共享的 RotatingFileAppender（时间轮转）
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("concurrent_time.log");

    // 禁用清理以测试并发时间轮转
    let appender = Arc::new(
        RotatingFileAppender::with_config(&log_path, Some(200), RotationPeriod::Daily)
            .expect("创建失败")
            .with_max_files(0), // 禁用清理，确保不丢失日志
    );

    // 设置 last_rotation_time 为昨天
    let yesterday = time::OffsetDateTime::now_utc() - Duration::days(1);
    appender.set_last_rotation_time_for_test(yesterday);

    let mut handles = vec![];

    // WHEN: 4 个线程并发写入
    for thread_id in 0..4 {
        let appender_clone = Arc::clone(&appender);
        let handle = thread::spawn(move || {
            for msg_id in 0..25 {
                let event = LogEvent::new(
                    Level::Info,
                    "concurrent_time",
                    format!("T{}-M{}", thread_id, msg_id),
                );
                appender_clone.write(&event).expect("写入失败");
            }
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().expect("线程 join 失败");
    }
    appender.flush().expect("刷新失败");

    // THEN: 所有日志都应被写入（共 100 条）
    let mut total_lines = 0;
    for entry in std::fs::read_dir(temp_dir.path()).unwrap().flatten() {
        if entry
            .file_name()
            .to_str()
            .map(|s| s.contains("concurrent_time"))
            .unwrap_or(false)
        {
            let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
            total_lines += content.lines().count();
        }
    }

    assert_eq!(total_lines, 100, "应有 100 条日志");
}

/// IT-10.2-005: Builder with_time_rotation() 测试 (AC-10.2.8 #19, #20)
#[test]
fn test_builder_with_time_rotation() {
    use log4r::RotationPeriod;

    // GIVEN/WHEN: 使用 Builder 设置时间轮转
    let builder = log4r::Log4rBuilder::new()
        .with_file("logs/test.log")
        .with_time_rotation(RotationPeriod::Daily);

    // THEN: Builder 应成功创建
    let _ = builder;

    // 测试同时设置大小和时间轮转
    let builder = log4r::Log4rBuilder::new()
        .with_file("logs/test.log")
        .with_rotation("10MB")
        .with_time_rotation(RotationPeriod::Daily);

    let _ = builder;
}

/// IT-10.2-006: 配置文件 rotation 初始化测试 (AC-10.2.2 #5)
#[test]
fn test_config_rotation_parsing() {
    use log4r::config::FileConfig;
    use log4r::RotationPeriod;

    // GIVEN: 包含 rotation 的 TOML 配置
    let toml_str = r#"
enabled = true
path = "logs/app.log"
rotation = "daily"
"#;

    // WHEN: 反序列化
    let config: FileConfig = toml::from_str(toml_str).expect("解析失败");

    // THEN: rotation 应正确解析
    assert_eq!(config.rotation, Some(RotationPeriod::Daily));

    // 测试 hourly
    let toml_str = r#"
enabled = true
path = "logs/app.log"
rotation = "hourly"
"#;
    let config: FileConfig = toml::from_str(toml_str).expect("解析失败");
    assert_eq!(config.rotation, Some(RotationPeriod::Hourly));

    // 测试 weekly
    let toml_str = r#"
enabled = true
path = "logs/app.log"
rotation = "weekly"
"#;
    let config: FileConfig = toml::from_str(toml_str).expect("解析失败");
    assert_eq!(config.rotation, Some(RotationPeriod::Weekly));
}

/// IT-10.2-007: 轮转失败降级测试 (AC-10.2.11 #23)
#[test]
fn test_time_rotation_graceful_degradation() {
    use log4r::RotationPeriod;
    use time::Duration;

    // GIVEN: 正常的 RotatingFileAppender（时间轮转）
    let temp_dir = TempDir::new().expect("创建临时目录失败");
    let log_path = temp_dir.path().join("graceful_time.log");

    let appender = RotatingFileAppender::with_time_rotation(&log_path, RotationPeriod::Daily)
        .expect("创建失败");

    // 设置 last_rotation_time 为昨天
    let yesterday = time::OffsetDateTime::now_utc() - Duration::days(1);
    appender.set_last_rotation_time_for_test(yesterday);

    // WHEN: 正常写入（验证不会 panic）
    for i in 0..30 {
        let event = LogEvent::new(Level::Info, "test", format!("Graceful msg {}", i));
        let result = appender.write(&event);
        // THEN: 写入应成功，不应 panic
        assert!(result.is_ok(), "写入应成功");
    }

    appender.flush().expect("刷新失败");
}
